package com.inspectortime.service;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dom4j.Document;
import org.dom4j.Element;

import com.inspectortime.util.XMLUtils;

/**
 * An implementation that goes to a geonames.org web service to retrieve zip
 * code data, then parses it using a DOM parser. The SAX impl is slightly faster
 * and more memory-efficient.
 * 
 * @author mKvalsvik
 * 
 */
public class DomZipCodeSearchServiceImpl extends AbstractZipCodeSearchService {

	protected static Log log = LogFactory
			.getLog(DomZipCodeSearchServiceImpl.class);

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.inspectortime.service.ZipCodeSearchService#findDistanceInMiles(java
	 * .lang.String, java.lang.String)
	 */
	public double findDistanceInMiles(String zipCodeA, String zipCodeB) {
		ZipCodeInfo zipA = this.lookupZipCode(zipCodeA);
		ZipCodeInfo zipB = this.lookupZipCode(zipCodeB);
		return zipA.distanceTo(zipB);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.inspectortime.service.ZipCodeSearchService#lookupZipCode(java.lang
	 * .String)
	 */
	public ZipCodeInfo lookupZipCode(String zipCode) {
		// http://ws.geonames.org/postalCodeSearch?postalcode=30012&maxRows=10&
		// country=US
		String webServiceUrl = "http://ws.geonames.org/postalCodeSearch";
		String url = webServiceUrl + "?postalcode=" + zipCode + "&country=US";

		HttpClient client = new HttpClient();
		HttpMethod method = new GetMethod(url);
		try {
			client.executeMethod(method);
		} catch (IOException e) {
			throw new RuntimeException(e);
		}

		Document doc = null;
		try {
			doc = XMLUtils.load("lookupZipCode", method
					.getResponseBodyAsStream());
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
		Element root = doc.getRootElement();
		Element codeElement = (Element) root.elementIterator("code").next();
		return this.parseCodeElement(codeElement);
	}

	private ZipCodeInfo parseCodeElement(Element codeElement) {
		ZipCodeInfo info = new ZipCodeInfo();
		info.setCity(codeElement.elementText("name"));
		info.setCountyCode(codeElement.elementText("adminCode2"));
		info.setCountyName(codeElement.elementText("adminName2"));
		String distanceStr = codeElement.elementText("distance");
		if (distanceStr != null) {
			info.setOptionalDistanceToSource(Float.valueOf(distanceStr));
		}
		info.setLatitude(Float.valueOf(codeElement.elementText("lat")));
		info.setLongitude(Float.valueOf(codeElement.elementText("lng")));
		info.setStateCode(codeElement.elementText("adminCode1"));
		info.setStateName(codeElement.elementText("adminName1"));
		info.setZipCode(codeElement.elementText("postalcode"));
		return info;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.inspectortime.service.ZipCodeSearchService#findNearbyZipCodes(java
	 * .lang.String, int)
	 */
	public List<ZipCodeInfo> findNearbyZipCodes(String zipCode,
			int radiusInMiles) {
		//http://ws.geonames.org/findNearbyPostalCodes?postalcode=30341&country=
		// US&radius=80&maxRows=500
		int maxRows = 500;
		final float KM_PER_US_MILE = 1.609f;
		float radiusInKm = radiusInMiles * KM_PER_US_MILE;
		String webServiceUrl = "http://ws.geonames.org/findNearbyPostalCodes";
		String url = webServiceUrl + "?postalcode=" + zipCode
				+ "&country=US&radius=" + radiusInKm + "&maxRows=" + maxRows;
		HttpClient client = new HttpClient();
		HttpMethod method = new GetMethod(url);
		Date startTime = new Date();
		try {
			client.executeMethod(method);
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
		Date endTime = new Date();

		Document doc = null;
		try {
			// TODO: This is too slow - over 3 sec for 500 codes on my laptop -
			// optimize by using SAX parser
			doc = XMLUtils.load("findNearbyPostalCodes", method
					.getResponseBodyAsStream());
		} catch (IOException e) {
			throw new RuntimeException(e);
		}

		Element root = doc.getRootElement();
		List<ZipCodeInfo> results = new ArrayList<ZipCodeInfo>();
		Iterator iter = root.elementIterator("code");
		while (iter.hasNext()) {
			Element codeElement = (Element) iter.next();
			ZipCodeInfo info = this.parseCodeElement(codeElement);
			results.add(info);
		}

		// Stats
		Date parseEndTime = new Date();
		long duration = endTime.getTime() - startTime.getTime();
		long parseTime = parseEndTime.getTime() - endTime.getTime();
		log.debug(webServiceUrl + ": Got " + root.elements().size()
				+ " zip codes within " + radiusInMiles + " miles of " + zipCode
				+ " in " + duration + "ms, parsed in " + parseTime + "ms.");

		return results;

	}
	
	

}
